package com.ayf.payment.game.api.excel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.enums.WriteDirectionEnum;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.ayf.payment.game.api.exception.LogicalException;
import com.swwx.charm.commons.lang.utils.LogPortal;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.List;

/**
 * @Description: Excel工具类
 * @Author: zh-liang
 * @Date : 2020-04-16 20:01
 */

public class ExcelUtils {

    /**
     * Excel文件上传
     *
     * @param file
     * @param clazz
     * @param readListener
     */
    public static void upload(String traceId, MultipartFile file, int rowNum, Class clazz, ReadListener readListener) {
        if (rowNum < 1) {
            throw new LogicalException("[{}]Excel文件上传时,开始读取的行数必须大于等于1,当前为[{}]", traceId, rowNum);
        }
        try {
            EasyExcel.read(file.getInputStream(), clazz, readListener).headRowNumber(rowNum).sheet().autoTrim(false).doRead();
        } catch (IOException e) {
            throw new LogicalException("[{}]Excel文件上传时读取发生[{}]异常,异常信息为[{}]", traceId, e.getClass().getSimpleName(), e.getMessage());
        }
    }

    /**
     * 简单的数据按照模板规则导出到Excel
     *
     * @param traceId      业务跟踪id
     * @param data         需要导出的数据
     * @param fileName     导出的文件名称
     * @param templateName 模板名称
     * @param response     响应
     */
    public static void simpleFillExport(String traceId, Object data, String fileName, String templateName,HttpServletResponse response) {
        if (templateName.substring(templateName.lastIndexOf(".")+1,templateName.length()).equalsIgnoreCase("xlsx")){
            simpleWriter(traceId, data, fileName, templateName, response, "DO_FILL",ExcelTypeEnum.XLSX);
        } else {
            simpleWriter(traceId, data, fileName, templateName, response, "DO_FILL",ExcelTypeEnum.XLSX);
        }
    }

    /**
     * 将list数据按照模板规则导出到Excel(分多次 填充 会使用文件缓存 省内存)
     *
     * @param traceId      业务跟踪id
     * @param data         需要导出的数据
     * @param fileName     导出的文件名称
     * @param templateName 模板名称
     * @param response     响应
     */
    public static void listFillExport(String traceId, Object data, String fileName, String templateName,HttpServletResponse response) {
        if (templateName.substring(templateName.lastIndexOf(".")+1,templateName.length()).equalsIgnoreCase("xlsx")){
            simpleWriter(traceId, data, fileName, templateName, response, "FILL",ExcelTypeEnum.XLSX);
        } else {
            simpleWriter(traceId, data, fileName, templateName, response, "FILL",ExcelTypeEnum.XLS);
        }
    }


    /**
     * 将list数据按照模板规则导出到本地
     *
     * @param traceId      业务跟踪id
     * @param data         需要导出的数据
     * @param fileName     导出的文件 路径+名称
     * @param templateName 模板名称
     */
    public static void listFill(String traceId, Object data, String fileName, String templateName) {
        try {
            InputStream templateInputStream = getTemplateFileName(templateName);
            ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateInputStream).build();
            WriteSheet writeSheet = EasyExcel.writerSheet().build();
            excelWriter.fill(data, writeSheet);
            // 关闭流
            excelWriter.finish();
        } catch (Exception e) {
            LogPortal.error("[{}]导出Excel到本地时发生[{}]异常,异常信息为[{}]", e, traceId, e.getClass().getSimpleName(),
                    e.getMessage());
        }
    }

    /**
     * 简单的数据导出Excel
     *
     * @param traceId      业务跟踪id
     * @param data         需要导出的数据
     * @param fileName     导出的文件名称
     * @param templateName 模板名称
     * @param response     响应
     * @param writeType    写入方式
     */
    private static void simpleWriter(String traceId, Object data, String fileName, String templateName, HttpServletResponse response,String writeType,ExcelTypeEnum excelTypeEnum) {
        try {
            ServletOutputStream outputStream = getOutputStream(response, fileName);
            // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替
            // 填充list 的时候还要注意 模板中{.} 多了个点 表示list
            InputStream templateInputStream = getTemplateFileName(templateName);
            if ("DO_FILL".equals(writeType)) {
                //方案1 一下子全部放到内存里面 并填充
                //这里 会填充到第一个sheet， 然后文件流会自动关闭
                EasyExcel.write(outputStream).withTemplate(templateInputStream).excelType(excelTypeEnum).sheet().doFill(data);
            } else if ("FILL".equals(writeType)) {
                // 方案2 分多次 填充 会使用文件缓存（省内存）
                ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(templateInputStream).excelType(excelTypeEnum).build();
                WriteSheet writeSheet = EasyExcel.writerSheet().build();
                excelWriter.fill(data, writeSheet);
                excelWriter.finish();
            }
        } catch (Exception e) {
            LogPortal.error("{} 导出Excel时发生[{}]异常",e,traceId,e.getClass().getSimpleName());
            throw new LogicalException("[{}]导出Excel时发生[{}]异常,异常信息为[{}]",traceId, e.getClass().getSimpleName(), e.getMessage());
        }
    }

    /**
     * 数据量小的复杂的数据导出Excel
     *
     * @param traceId      业务跟踪id
     * @param listData     需要导出的list数据
     * @param simpleData   简单数据
     * @param fileName     导出的文件名称
     * @param templateName 模板名称
     * @param response     响应
     */
    public static void smallComplexFill(String traceId, Object listData, Object simpleData, String fileName, String templateName,
                                        HttpServletResponse response) {
        try {
            ServletOutputStream outputStream = getOutputStream(response, fileName);
            // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替
            // 填充list 的时候还要注意 模板中{.} 多了个点 表示list
            InputStream templateInputStream = getTemplateFileName(templateName);
            ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(templateInputStream).build();
            WriteSheet writeSheet = EasyExcel.writerSheet().build();
            // 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行，然后下面的数据往后移动。默认 是false，会直接使用下一行，如果没有则创建。
            // forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了，所以慎用
            // 简单的说 如果你的模板有list,且list不是最后一行，下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存
            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
            excelWriter.fill(listData, fillConfig, writeSheet);
            excelWriter.fill(simpleData, writeSheet);
            // 关闭流
            excelWriter.finish();
        } catch (Exception e) {
            throw new LogicalException("[{}]导出Excel时发生[{}]异常,异常信息为[{}]", traceId, e.getClass().getSimpleName(), e.getMessage());
        }
    }

    /**
     * 数据量大的复杂的数据导出Excel
     *
     * @param traceId        业务跟踪id
     * @param listData       需要导出的list数据
     * @param listBeforeData list数据之前的数据
     * @param listAfterData  list数据之后的数据
     * @param fileName       导出的文件名称
     * @param templateName   模板名称
     * @param response       响应
     */
    public static void bigComplexFill(String traceId, Object listData, Object listBeforeData,
                                      List<List<String>> listAfterData,
                                      String fileName,
                                      String templateName, HttpServletResponse response) {
        try {
            ServletOutputStream outputStream = getOutputStream(response, fileName);
            // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替
            // 填充list 的时候还要注意 模板中{.} 多了个点 表示list
            InputStream templateInputStream = getTemplateFileName(templateName);
            ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(templateInputStream).build();
            WriteSheet writeSheet = EasyExcel.writerSheet().build();
            // 直接写入数据
            excelWriter.fill(listData, writeSheet);
            if (listBeforeData != null) {
                // 写入list之前的数据
                excelWriter.fill(listBeforeData, writeSheet);
            }
            if (listAfterData != null) {
                // list 后面还有个统计 需要手动写入
                // 这里是write
                excelWriter.write(listAfterData, writeSheet);
            }
            // 关闭流
            excelWriter.finish();
        } catch (Exception e) {
            throw new LogicalException("[{}]导出Excel时发生[{}]异常,异常信息为[{}]", traceId, e.getClass().getSimpleName(), e.getMessage());
        }
    }

    /**
     * 横向数据导出Excel
     *
     * @param traceId      业务跟踪id
     * @param listData     需要导出的list数据
     * @param simpleData   简单数据
     * @param fileName     导出的文件名称
     * @param templateName 模板名称
     * @param response     响应
     */
    public static void horizontalFill(String traceId, Object listData, Object simpleData, String fileName, String templateName,
                                      HttpServletResponse response) {
        try {
            ServletOutputStream outputStream = getOutputStream(response, fileName);
            // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替
            // 填充list 的时候还要注意 模板中{.} 多了个点 表示list
            InputStream templateInputStream = getTemplateFileName(templateName);
            ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(templateInputStream).build();
            WriteSheet writeSheet = EasyExcel.writerSheet().build();
            FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
            excelWriter.fill(listData, fillConfig, writeSheet);
            if (simpleData != null) {
                excelWriter.fill(simpleData, writeSheet);
            }
            excelWriter.finish();
        } catch (Exception e) {
            throw new LogicalException("[{}]导出Excel时发生[{}]异常,异常信息为[{}]", traceId, e.getClass().getSimpleName(), e.getMessage());
        }
    }


    /**
     * Excel模板下载
     *
     * @param traceId  业务跟踪id
     * @param fileName 预下载的文件名
     * @param response 响应
     */
    public static void downTemplate(String traceId, String fileName, HttpServletResponse response) {
        try {
            ClassPathResource resource = new ClassPathResource("templates/excel/download/" + fileName);
            InputStream fis = resource.getInputStream();
            byte[] buffer = FileCopyUtils.copyToByteArray(fis);
            fis.close();
            // 清空response
            response.reset();
            // 设置response的Header
            ServletOutputStream outputStream = getOutputStream(response, fileName);
            outputStream.write(buffer);
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            throw new LogicalException("[{}]Excel模板下载时发生[{}]异常,异常信息为[{}]", traceId, e.getClass().getSimpleName(), e.getMessage());
        }
    }

    /**
     * 获取输出流
     *
     * @param response 响应
     * @param fileName 文件名
     * @return 输出流
     * @throws IOException
     */
    private static ServletOutputStream getOutputStream(HttpServletResponse response, String fileName) throws IOException {
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf8");
        // 这里URLEncoder.encode可以防止中文乱码
        fileName = URLEncoder.encode(fileName, "UTF-8");
        response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
        return response.getOutputStream();
    }

    /**
     * 获取模板路径
     *
     * @param templateName 模板名称
     * @return 模板流
     */
    private static InputStream getTemplateFileName(String templateName) throws IOException {
        ClassPathResource resource = new ClassPathResource("excel/" + templateName);
        return resource.getInputStream();
    }
}
